#include "beatshooter.h"
#include <csugl.h>

#include "ecs.h"
#include "scene.h"
#include "sprite_renderer.h"
#include "playercontroller.h"
#include "cloudcontroller.h"
#include "cameracontroller.h"

#include <glad/glad.h>
#include <glog/logging.h>
#include <glfw/glfw3.h>

#include <imgui.h>
#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_opengl3.h>

#include <glm/gtc/type_ptr.hpp>

int beat_shooter() {
    auto app = csugl::singleton<csugl::Application>::getInstance();
    auto win = &app->GetWindow();

    SGT_Ref_Str_AM(csugl::Shader)->add("sprite_shader", csugl::MakeRef<csugl::Shader>("../assets/shader/sprite.vert", "../assets/shader/sprite.frag"));

    // asset loading
    auto il = csugl::MakeRef<csugl::il::image_loader>();
    {
        il->pre_load("../assets/texture/cloud.png");
        il->pre_load("../assets/texture/heart.png");
        il->pre_load("../assets/texture/unknow_tex.bmp");
    }

    SGT_Ref_Str_AM(csugl::Texture2D)->add("unknow_tex", csugl::Texture2D::Create(il->load("unknow_tex")));

    auto mainScene = Scene::Create();

    // sprite
    auto& sp0 = *mainScene->CreateGameObject();
    sp0.AddComponent(ActiveComponent());
    sp0.AddComponent(TagComponent("cloud0"));
    sp0.AddComponent(TransformComponent{{glm::vec3(-56.f, 10.f, -1.0f), glm::vec3{0.f}, glm::vec3{0.2f}}});
    sp0.AddComponent(SpriteRendererComponent{{glm::vec2{.0f, .0f}, glm::vec2{1.f, 1.f}, csugl::Texture2D::Create(il->load("cloud"))}, 0});
    sp0.AddComponent(CloudComponent(2.5f, 6.0f, 2.0f));

    auto& sp0_copy = *mainScene->CreateGameObject();
    sp0_copy.AddComponent(ActiveComponent());
    sp0_copy.AddComponent(TagComponent("cloud1"));
    sp0_copy.AddComponent(TransformComponent{{glm::vec3(-26.f, 6.f, -1.0f), glm::vec3{0.f}, glm::vec3{0.1f}}});
    sp0_copy.AddComponent(SpriteRendererComponent{{glm::vec2{.0f, .0f}, glm::vec2{1.f, 1.f}, csugl::Texture2D::Create(il->load("cloud"))}, 1});
    sp0_copy.AddComponent(CloudComponent(2.9f, 1.0f, 3.2f));

    auto& sp1 = *mainScene->CreateGameObject();
    sp1.AddComponent(ActiveComponent());
    sp1.AddComponent(TagComponent("heart0"));
    sp1.AddComponent(TransformComponent{{glm::vec3(-28.f, -16.f, .0f), glm::vec3{0.f}, glm::vec3{0.05f}}});
    sp1.AddComponent(SpriteRendererComponent{{glm::vec2{.0f, .0f}, glm::vec2{1.f, 1.f}, csugl::Texture2D::Create(il->load("heart"))}, 2});

    auto& sp1_copy = *mainScene->CreateGameObject();
    sp1_copy.AddComponent(ActiveComponent());
    sp1_copy.AddComponent(TagComponent("heart1"));
    sp1_copy.AddComponent(TransformComponent{{glm::vec3(-38.f, -26.f, .0f), glm::vec3{0.f}, glm::vec3{0.1f}}});
    sp1_copy.AddComponent(SpriteRendererComponent{{glm::vec2{.0f, .0f}, glm::vec2{1.f, 1.f}, csugl::Texture2D::Create(il->load("heart"))}, 3});

    auto& sp2 = *mainScene->CreateGameObject();
    sp2.AddComponent(ActiveComponent());
    sp2.AddComponent(TagComponent("player"));
    sp2.AddComponent(TransformComponent{{glm::vec3(-18.f, -16.f, .0f), glm::vec3{0.f}, glm::vec3{10.0f}}});
    sp2.AddComponent(SpriteRendererComponent{{glm::vec2{.0f, .0f}, glm::vec2{1.f, 1.f}, SGT_Ref_Str_AM(csugl::Texture2D)->get("unknow_tex")}, 4});
    sp2.AddComponent(PlayerComponent(30.5f));

    // camera
    csugl::Transform camera_trans(glm::vec3{.0f, .0f, 2.f}, glm::vec3{.0f}, glm::vec3{1.0f});
    auto& camera = *mainScene->CreateGameObject();
    camera.AddComponent(ActiveComponent());
    camera.AddComponent(TagComponent("camera"));
    camera.AddComponent(TransformComponent{camera_trans});
    camera.AddComponent(CameraComponent{csugl::MakeRef<csugl::OrthographicCamera>(camera_trans,
                                     static_cast<float>(win->GetSize().y) / win->GetSize().x,
                                     100.0f, 0.01f, 1000.0f)});
    camera.AddComponent(CameraControllerComponent(100.f));

    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
    // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
    (void) io;
    ImGui_ImplGlfw_InitForOpenGL(win->GetGLFWwindow(), true);
    ImGui_ImplOpenGL3_Init("#version 330 core");
    ImGui::StyleColorsDark();

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glm::vec4 clear_color{0.81f, 0.92f, 1.0f, 1.0f};

    SpriteRendererSystem sprdsys(mainScene);
    PlayerController plsys(mainScene);
    CloudController cloudsys(mainScene);
    CameraController camerasys(mainScene);
    sprdsys.start();
    cloudsys.start();
    camerasys.start();

    csugl::LowpTime::init();
    while (app->isOpen())
    {
        glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        // static bool show_demo = false;
        // ImGui::ShowDemoWindow(&show_demo);

        // logic
        plsys.update();
        cloudsys.update();
        camerasys.update();

        GameObject* cur_obj = nullptr;
        ImGui::Begin("Scene");
        {
            static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
            static int node_selected = -1;
            int i = 0;
            for (auto &obj : *mainScene)
            {
                ImGuiTreeNodeFlags node_flags = base_flags;
                if (node_selected == i)
                {
                    node_flags |= ImGuiTreeNodeFlags_Selected;
                }
                node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet
                ImGui::TreeNodeEx((void *)(intptr_t)i, node_flags, "%s", obj->GetComponent<TagComponent>()->tag.c_str());
                if (ImGui::IsItemClicked())
                {
                    node_selected = i;
                }
                i++;
            }
            if (node_selected != -1)
            {
                cur_obj = (mainScene->begin() + node_selected)->get();
            }
        }
        ImGui::End();

        // logic GUI
        ImGui::Begin("Properties");
        if (cur_obj)
        {
            { 
                // active component
                auto active_c = cur_obj->GetComponent<ActiveComponent>();
                ImGui::Checkbox("Active", &active_c->active);
            }
            ImGui::Separator();
            {
                auto tag_c = cur_obj->GetComponent<TagComponent>();
                // tag component
                char buf[512];
                memset(buf, 0, 512);
                strcpy(buf, tag_c->tag.c_str());
                ImGui::InputText("Tag", buf, 512);
                tag_c->tag = buf;
            }
            ImGui::Separator();
            if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen))
            {
                auto trans_c = cur_obj->GetComponent<TransformComponent>();
                ImGui::DragFloat3("Position", glm::value_ptr(trans_c->transform.position), 0.1f, 0.0f, 0.0f, "%.2f");
                ImGui::DragFloat4("Rotation", glm::value_ptr(trans_c->transform.rotation), 0.01f, 0.0f, 0.0f, "%.2f");
                ImGui::DragFloat3("Scale", glm::value_ptr(trans_c->transform.scale), 0.001f, 0.0f, 0.0f, "%.3f");
            }
            ImGui::Separator();
            auto sprite_c = cur_obj->GetComponent<SpriteRendererComponent>();
            if (sprite_c && ImGui::CollapsingHeader("Sprite Renderer", ImGuiTreeNodeFlags_DefaultOpen))
            {
                ImGui::Text("Albedo");
                auto tex_ratio = (float)sprite_c->sprite.texture->height / sprite_c->sprite.texture->width;
                ImGui::Image(reinterpret_cast<ImTextureID>(sprite_c->sprite.texture->tex_ID),
                             {128.f, tex_ratio * 128}, {0, 1}, {1, 0});
                ImGui::InputInt("Sort Layer", &sprite_c->sort_layer);
            }
            auto cameractl_c = cur_obj->GetComponent<CameraControllerComponent>();
            if(cameractl_c && ImGui::CollapsingHeader("Camera Controller", ImGuiTreeNodeFlags_DefaultOpen))
            {
                ImGui::DragFloat("zoom Speed", &cameractl_c->zoomSpeed, 5.f, 0.0f, 0.0f, "%.2f");
            }
            auto player_c = cur_obj->GetComponent<PlayerComponent>();
            if (player_c && ImGui::CollapsingHeader("Player Controller", ImGuiTreeNodeFlags_DefaultOpen))
            {
                ImGui::DragFloat("move Speed", &player_c->moveSpeed, .01f, .0f, .0f, "%.2f");
            }
        }
        ImGui::End();

        // render
        sprdsys.update();

        // render GUI
        {
            ImGui::Begin("State");
            static float frame_show_interval = 0.f;
            static float frames = 1.f / csugl::LowpTime::deltaTime();
            static int batchs = sprdsys.batch_nums();
            frame_show_interval += csugl::LowpTime::deltaTime();
            if (frame_show_interval >= 1.0f)
            {
                frames = 1.f / csugl::LowpTime::deltaTime();
                batchs = sprdsys.batch_nums();
                frame_show_interval = .0f;
            }
            ImGui::Text("Frames: %.2f fps", frames);
            ImGui::Text("Batchs: %d", batchs);
            ImGui::End();
        }

        // ImGui render
        {
            ImGui::Render();
            ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
            ImGui::EndFrame();
            io = ImGui::GetIO();
            if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
                const auto backup = win->GetGLFWwindow();
                ImGui::UpdatePlatformWindows();
                ImGui::RenderPlatformWindowsDefault();
                glfwMakeContextCurrent(backup);
            }
        }

        win->Display();
        csugl::LowpTime::update();
    }

    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
    
    csugl::singleton<csugl::Application>::destroy();

    return 0;
}
